<?php
/**
 * @author Jumper Swordwave
 * date: 2021/04/22
 */
namespace HyperfTest\Cases;

use PHPUnit\Framework\TestCase;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Redis\Redis;
use App\Game\Service\{RoomService, UserService};
use App\Game\Core\GameStatus;

/**
 * 房间服务测试
 */
class RoomServiceTest extends TestCase 
{
    protected function setUp() : void 
    {
        $this->redis = ApplicationContext::getContainer()->get(Redis::class);
        $this->roomService = ApplicationContext::getContainer()->get(RoomService::class);
        $this->userService = ApplicationContext::getContainer()->get(UserService::class);
        
    }

    protected function tearDown() : void 
    {
        $this->roomService->clear();
        $this->userService->clear();
    }

    protected function getUsers($ids)
    {
        $users = [];
        foreach($ids as $id) {
            $user = $this->userService->findUserById(intval($id));
            if ($user == null) {
                $user = $this->userService->newUser(intval($id), ['nickname'=> 'name' . $id]);
            }
            $user->reset();
            $this->userService->saveUser($user);
            $users[] = $user;
        }
        return $users;
    }

    protected function getRoom($id)
    {
        $room = $this->roomService->findRoom(intval($id));
        if ($room == null) {
            $room = $this->roomService->newRoom();
        }

        $room->reset();
        $this->roomService->saveRoom($room);

        return $room;
    }
    
    /**
     * 所有成员都已就绪,返回true
     */
    public function testAllReadyOk()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }

        $result = $this->roomService->allReady($room);
        $this->assertTrue($result);
    }

    /**
     * 有成员未准备好,返回false
     */
    public function testAllReadyNo()
    {
        $room = $this->getRoom(2);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
            if ($user->id == 3) {   //设置3号用户未就绪
                $user->status = GameStatus::USER_WAITING;
                $this->userService->saveUser($user);
            }
        }

        $result = $this->roomService->allReady($room);
        $this->assertFalse($result);
    }

    /**
     * 房间内人员未满,返回false
     */
    public function testAllReadyUnfull()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }

        $result = $this->roomService->allReady($room);
        $this->assertFalse($result);
    }

    /**
     * 游戏开始
     */
    public function testGameStart()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }
        
        $room = $this->roomService->gameStart($room);
        $this->assertNotNull($room);
        $this->assertSame($room->status, GameStatus::ROOM_CALL);
        $this->assertCount(3, $room->holes);
        $hands = $room->hands;
        foreach($hands as $cards) {
            $this->assertCount(17, $cards);
        }

        $users = $this->userService->findUsers($room->users);
        foreach($users as $user) {
            $this->assertSame($user->status, GameStatus::USER_PLAYING);
        }
    }

    /**
     * 叫地主,成功
     */
    public function testGameCallOk()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }
        
        $room = $this->roomService->gameStart($room);
        $this->assertNotNull($room);

        //第一个叫地主
        $uid = $room->getCurrentUid();
        $user = $this->userService->findUserById($uid);
        $this->assertNotNull($user);
        $room = $this->roomService->gameCall($room, $user, true);
        $this->assertNotNull($room);
        $this->assertSame($room->master, $room->getUserPosition($uid));

        //第二位不抢地主跳过
        $s_uid = $room->getCurrentUid();
        $this->assertNotSame($uid, $s_uid);
        $s_user = $this->userService->findUserById($s_uid);
        $this->assertNotNull($s_user);
        $room = $this->roomService->gameCall($room, $s_user, false);
        $this->assertNotNull($room);
        $this->assertNotSame($room->master, $room->getUserPosition($s_uid));
        
        //第三位抢地主
        $t_uid = $room->getCurrentUid();
        $this->assertNotSame($s_uid, $t_uid);
        $t_user = $this->userService->findUserById($t_uid);
        $this->assertNotNull($t_user);
        $room = $this->roomService->gameCall($room, $t_user, true);
        $this->assertNotNull($room);
        $this->assertSame($room->master, $room->getUserPosition($t_uid));

        $this->assertSame($room->status, GameStatus::ROOM_PLAYING); //抢地主结束进入游戏阶段
        $hands = $room->hands;
        $master_hands = $hands[$room->master - 1];
        $this->assertCount(20, $master_hands);  //检查地主手牌是否为20张
        $this->assertSame($room->master, $room->current);   //出牌人是否为地主

        //结束后继续叫地主
        $room = $this->roomService->gameCall($room, $user, true);
        $this->assertNull($room);
    }
    
    /**
     * 叫地主,失败
     */
    public function testGameCallError()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }
        
        $room = $this->roomService->gameStart($room);
        $this->assertNotNull($room);

        $uid = $room->getCurrentUid();
        $call_user = null;
        foreach($users as $user) {
            if ($uid != $user->id) {
                $call_user = $user;
                break;
            }
        }
        
        //未轮到的用户叫地主
        $this->assertNotNull($call_user);
        $room = $this->roomService->gameCall($room, $call_user, true);
        $this->assertNull($room);
    }

    /**
     * 出牌
     */
    public function testOutCards()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }
        
        $room = $this->roomService->gameStart($room);
        $this->assertNotNull($room);

        //第一个叫地主
        $uid = $room->getCurrentUid();
        $user = $this->userService->findUserById($uid);
        $this->assertNotNull($user);
        $room = $this->roomService->gameCall($room, $user, true);
        $this->assertNotNull($room);
        $this->assertSame($room->master, $room->getUserPosition($uid));

        //第二位不抢地主跳过
        $s_uid = $room->getCurrentUid();
        $this->assertNotSame($uid, $s_uid);
        $s_user = $this->userService->findUserById($s_uid);
        $this->assertNotNull($s_user);
        $room = $this->roomService->gameCall($room, $s_user, false);
        $this->assertNotNull($room);
        $this->assertNotSame($room->master, $room->getUserPosition($s_uid));
        
        //第三位抢地主
        $t_uid = $room->getCurrentUid();
        $this->assertNotSame($s_uid, $t_uid);
        $t_user = $this->userService->findUserById($t_uid);
        $this->assertNotNull($t_user);
        $room = $this->roomService->gameCall($room, $t_user, true);
        $this->assertNotNull($room);
        $this->assertSame($room->master, $room->getUserPosition($t_uid));

        $this->assertSame($room->current, $room->master);
        //玩家起手不出牌
        $r_room = $this->roomService->outCards($room, $t_user, []);
        $this->assertNull($r_room);
        
        //非出牌玩家出牌
        $r_room = $this->roomService->outCards($room, $s_user, [1,2,3]);
        $this->assertNull($r_room);

        //地主玩家起手出牌
        $r_room = $this->roomService->outCards($room, $t_user, [1,2,3]);
        $this->assertNotNull($r_room);
        
        //跟牌玩家跳过不出
        $room = $r_room;
        $r_room = $this->roomService->outCards($room, $user, []);
        $this->assertNotNull($r_room);
        
        //第二位跟牌跳过
        $room = $r_room;
        $r_room = $this->roomService->outCards($room, $s_user, []);
        $this->assertNotNull($r_room);
        
        //两位玩家跳过后,回到起始玩家出牌,不可跳过
        $room = $r_room;
        $r_room = $this->roomService->outCards($room, $t_user, []);
        $this->assertNull($r_room);
        
        //重新出牌
        $r_room = $this->roomService->outCards($room, $t_user, [3,4,5]);
        $this->assertNotNull($r_room);
        
        //连续出牌
        $room = $r_room;
        $r_room = $this->roomService->outCards($room, $user, [7,8,9]);
        $this->assertNotNull($r_room);

        $room = $r_room;
        $r_room = $this->roomService->outCards($room, $s_user, [11,12]);
        $this->assertNotNull($r_room);

        $room = $r_room;
        $r_room = $this->roomService->outCards($room, $t_user, [33,34]);
        $this->assertNotNull($r_room);
    }

    public function testGameover()
    {
        $room = $this->getRoom(1);
        $users = $this->getUsers([1,2,3]);
        foreach($users as $user) {
            $r = $this->roomService->userJoin($room, $user);
            $this->assertTrue($r);
        }
        
        $room = $this->roomService->gameStart($room);
        $this->assertNotNull($room);

        //游戏未结束
        $r = $this->roomService->gameOver($room);
        $this->assertNull($r);
        
        $hands = $room->hands;
        $hands[0] = [];
        $room->hands = $hands;

        $r = $this->roomService->gameOver($room);
        $this->assertNotNull($r);
        $this->assertSame($r->status, GameStatus::ROOM_GAMEOVER);

        $users = $this->userService->findUsers($r->users);
        foreach($users as $user) {
            $this->assertSame($user->status, GameStatus::USER_GAMEOVER);
        }
    }
}